React-ning useSyncExternalStore hook'ini tashqi ma'lumotlar omborlarini sinxronlashtirish uchun chuqur o'rganish, jumladan, amalga oshirish strategiyalari, ishlash samaradorligi va ilg'or qo'llanilish holatlari.
React useSyncExternalStore: Tashqi ma'lumotlar ombori sinxronizatsiyasini o'zlashtirish
Zamonaviy React ilovalarida holatni samarali boshqarish juda muhim. React useState va useReducer kabi o'rnatilgan holatni boshqarish yechimlarini taqdim etsa-da, tashqi ma'lumotlar manbalari yoki uchinchi tomon holatni boshqarish kutubxonalari bilan integratsiya qilish yanada murakkabroq yondashuvni talab qiladi. Aynan shu yerda useSyncExternalStore yordamga keladi.
useSyncExternalStore nima?
useSyncExternalStore — bu React 18 da taqdim etilgan React hook'i bo'lib, u bir vaqtda renderlash (concurrent rendering) bilan mos keladigan tarzda tashqi ma'lumotlar manbalariga obuna bo'lish va ulardan o'qish imkonini beradi. Bu, ayniqsa, React tomonidan to'g'ridan-to'g'ri boshqarilmaydigan ma'lumotlar bilan ishlashda muhim ahamiyatga ega, masalan:
- Uchinchi tomon holatni boshqarish kutubxonalari: Redux, Zustand, Jotai va hk.
- Brauzer API'lari:
localStorage,IndexedDBva hk. - Tashqi ma'lumotlar manbalari: Server-sent events, WebSockets va hk.
useSyncExternalStore'dan oldin, tashqi omborlarni sinxronlashtirish, ayniqsa React'ning bir vaqtda renderlash xususiyatlari bilan ishlaganda, "tearing" (uzilish) va nomuvofiqliklarga olib kelishi mumkin edi. Bu hook tashqi ma'lumotlarni React komponentlaringizga ulash uchun standartlashtirilgan va samarali usulni taqdim etish orqali ushbu muammolarni hal qiladi.
Nima uchun useSyncExternalStore? Foydalari va afzalliklari
useSyncExternalStore'dan foydalanish bir nechta asosiy afzalliklarni taqdim etadi:
- Bir vaqtda ishlash xavfsizligi: Komponentingiz har doim tashqi omborning izchil ko'rinishini aks ettirishini ta'minlaydi, hatto bir vaqtda renderlash paytida ham. Bu sizning UI qismlaringiz nomuvofiq ma'lumotlarni ko'rsatishi mumkin bo'lgan "tearing" muammolarini oldini oladi.
- Samaradorlik: Ishlash uchun optimallashtirilgan, keraksiz qayta renderlashlarni minimallashtiradi. U o'zgarishlarga samarali obuna bo'lish va komponentni faqat kerak bo'lganda yangilash uchun React'ning ichki mexanizmlaridan foydalanadi.
- Standartlashtirilgan API: Asosiy amalga oshirishdan qat'i nazar, tashqi omborlar bilan ishlash uchun izchil va bashorat qilinadigan API'ni taqdim etadi.
- Kamroq andoza kod (Boilerplate): Tashqi omborlarga ulanish jarayonini soddalashtiradi, siz yozishingiz kerak bo'lgan maxsus kod miqdorini kamaytiradi.
- Moslik: Keng doiradagi tashqi ma'lumotlar manbalari va holatni boshqarish kutubxonalari bilan uzluksiz ishlaydi.
useSyncExternalStore qanday ishlaydi: Chuqur tahlil
useSyncExternalStore hook'i uchta argumentni qabul qiladi:
subscribe(callback: () => void): () => void: Tashqi ombor o'zgarganda xabardor qilinadigan callback'ni ro'yxatdan o'tkazadigan funksiya. U obunani bekor qilish uchun funksiya qaytarishi kerak. React omborda yangi ma'lumotlar borligini shu tarzda bilib oladi.getSnapshot(): T: Tashqi ombordan ma'lumotlarning oniy tasvirini (snapshot) qaytaradigan funksiya. Bu oniy tasvir React ma'lumotlar o'zgarganligini aniqlash uchun ishlatishi mumkin bo'lgan oddiy, o'zgarmas qiymat bo'lishi kerak.getServerSnapshot?(): T(Ixtiyoriy): Serverdagi ma'lumotlarning dastlabki oniy tasvirini qaytaradigan funksiya. Bu server tomonida renderlash (SSR) uchun server va mijoz o'rtasidagi izchillikni ta'minlash maqsadida ishlatiladi. Agar taqdim etilmasa, React serverda renderlash paytidagetSnapshot()'dan foydalanadi, bu barcha holatlar uchun ideal bo'lmasligi mumkin.
Bu argumentlarning birgalikda qanday ishlashining tahlili:
- Komponent o'rnatilganda (mount),
useSyncExternalStorecallback'ni ro'yxatdan o'tkazish uchunsubscribefunksiyasini chaqiradi. - Tashqi ombor o'zgarganda, u
subscribeorqali ro'yxatdan o'tgan callback'ni ishga tushiradi. - Callback React'ga komponentni qayta renderlash kerakligini bildiradi.
- Renderlash paytida,
useSyncExternalStoretashqi ombordan eng so'nggi ma'lumotlarni olish uchungetSnapshot'ni chaqiradi. - React joriy oniy tasvirni avvalgisi bilan solishtiradi. Agar ular farq qilsa, komponent yangi ma'lumotlar bilan yangilanadi.
- Komponent o'chirilganda (unmount), xotira sizib chiqishining oldini olish uchun
subscribetomonidan qaytarilgan obunani bekor qilish funksiyasi chaqiriladi.
Asosiy amalga oshirish misoli: localStorage bilan integratsiya
Keling, useSyncExternalStore'ni oddiy misol bilan qanday ishlatishni ko'rib chiqaylik: localStorage'ga qiymat yozish va o'qish.
import { useSyncExternalStore } from 'react';
function getLocalStorageItem(key: string): string | null {
try {
return localStorage.getItem(key);
} catch (error) {
console.error("Error accessing localStorage:", error);
return null; // `localStorage` mavjud bo'lmagani kabi potentsial xatolarni qayta ishlash.
}
}
function useLocalStorage(key: string): [string | null, (value: string) => void] {
const subscribe = (callback: () => void) => {
window.addEventListener('storage', callback);
return () => window.removeEventListener('storage', callback);
};
const getSnapshot = () => getLocalStorageItem(key);
const serverSnapshot = () => null; // Yoki SSR sozlamalaringizga mos bo'lsa, standart qiymat
const value = useSyncExternalStore(subscribe, getSnapshot, serverSnapshot);
const setValue = (newValue: string) => {
try {
localStorage.setItem(key, newValue);
// Boshqa yorliqlardagi yangilanishlarni ishga tushirish uchun joriy oynada 'storage' hodisasini yuborish.
window.dispatchEvent(new StorageEvent('storage', {
key: key,
newValue: newValue,
storageArea: localStorage,
} as StorageEventInit));
} catch (error) {
console.error("Error setting localStorage:", error);
}
};
return [value, setValue];
}
function MyComponent() {
const [name, setName] = useLocalStorage('name');
return (
<div>
<p>Hello, {name || 'World'}</p>
<input
type="text"
value={name || ''}
onChange={(e) => setName(e.target.value)}
/>
</div>
);
}
export default MyComponent;
Tushuntirish:
getLocalStorageItem: Potensial xatolarni bartaraf etib,localStorage'dan qiymatni xavfsiz tarzda olish uchun yordamchi funksiya.useLocalStorage:useSyncExternalStoreyordamidalocalStoragebilan ishlash mantig'ini o'z ichiga olgan maxsus hook.subscribe: Boshqa yorliq yoki oynadalocalStorageo'zgartirilganda ishga tushadigan'storage'hodisasini tinglaydi. Muhimi, biz yangi qiymatni o'rnatganimizdan so'ng, *bir xil* oynadagi yangilanishlarni to'g'ri ishga tushirish uchun "storage" hodisasini yuboramiz.getSnapshot:localStorage'dan joriy qiymatni qaytaradi.serverSnapshot: Server tomonida renderlash uchunnull(yoki standart qiymat) qaytaradi.setValue:localStorage'dagi qiymatni yangilaydi va boshqa yorliqlarga signal berish uchun "storage" hodisasini yuboradi.MyComponent: Nomni ko'rsatish va yangilash uchunuseLocalStoragehook'idan foydalanadigan oddiy komponent.
localStorage uchun muhim mulohazalar:
- Xatolarni qayta ishlash:
localStorageo'chirilgan yoki mavjud bo'lmagan (masalan, maxfiy ko'rish rejimida) kabi potentsial xatolarni bartaraf etish uchun har doimlocalStorage'ga kirishnitry...catchbloklariga o'rang. - Storage hodisalari:
'storage'hodisasi faqatlocalStorage*boshqa* yorliq yoki oynada o'zgartirilganda ishga tushadi, bir xil oynada emas. Shuning uchun, qiymatni o'rnatganimizdan so'ng biz yangiStorageEvent'ni qo'lda yuboramiz. - Ma'lumotlarni seriyalashtirish:
localStoragefaqat satrlarni saqlaydi. Murakkab ma'lumotlar tuzilmalarini seriyalashtirish va deseriyalashtirish uchunJSON.stringifyvaJSON.parse'dan foydalanishingiz kerak bo'lishi mumkin. - Xavfsizlik:
localStorage'da saqlaydigan ma'lumotlaringizga ehtiyot bo'ling, chunki u bir xil domendagi JavaScript kodi uchun ochiq. Maxfiy ma'lumotlarnilocalStorage'da saqlamaslik kerak.
Ilg'or qo'llanilish holatlari va misollar
1. Zustand (yoki boshqa holatni boshqarish kutubxonasi) bilan integratsiya
useSyncExternalStore'ni Zustand kabi global holatni boshqarish kutubxonasi bilan integratsiya qilish keng tarqalgan holatdir. Mana bir misol:
import { useSyncExternalStore } from 'react';
import { create } from 'zustand';
interface BearState {
bears: number
increase: (by: number) => void
}
const useStore = create<BearState>((set) => ({
bears: 0,
increase: (by) => set((state) => ({ bears: state.bears + by }))
}))
function BearCounter() {
const bears = useSyncExternalStore(
useStore.subscribe,
useStore.getState,
() => ({ bears: 0, increase: () => {} }) // Server oniy tasviri, standart holatni taqdim eting
).bears
return <h1>{bears} bears around here!</h1>
}
function Controls() {
const increase = useStore(state => state.increase)
return (<button onClick={() => increase(1)}>one bear</button>)
}
export { BearCounter, Controls }
Tushuntirish:
- Biz global holatni boshqarish uchun Zustand'dan foydalanmoqdamiz
useStore.subscribe: Bu funksiya Zustand omboriga obuna bo'ladi va ombor holati o'zgarganda qayta renderlashni ishga tushiradi.useStore.getState: Bu funksiya Zustand omborining joriy holatini qaytaradi.- Uchinchi parametr server tomonida renderlash (SSR) uchun standart holatni ta'minlaydi, bu esa mijoz tomonidagi JavaScript ishga tushgunga qadar komponentning serverda to'g'ri renderlanishini ta'minlaydi.
- Komponent
useSyncExternalStoreyordamida ayiqlar sonini oladi va uni render qiladi. Controlskomponenti Zustand setter'idan qanday foydalanishni ko'rsatadi.
2. Server-Sent Events (SSE) bilan integratsiya
useSyncExternalStore Server-Sent Events (SSE) yordamida serverdan keladigan real vaqtdagi ma'lumotlarga asoslanib komponentlarni samarali yangilash uchun ishlatilishi mumkin.
import { useSyncExternalStore, useState, useEffect, useCallback } from 'react';
function useSSE(url: string) {
const [data, setData] = useState(null);
const [eventSource, setEventSource] = useState(null);
useEffect(() => {
const newEventSource = new EventSource(url);
setEventSource(newEventSource);
newEventSource.onmessage = (event) => {
try {
const parsedData = JSON.parse(event.data);
setData(parsedData);
} catch (error) {
console.error("Error parsing SSE data:", error);
}
};
newEventSource.onerror = (error) => {
console.error("SSE error:", error);
};
return () => {
newEventSource.close();
setEventSource(null);
};
}, [url]);
const subscribe = useCallback((callback: () => void) => {
if (eventSource) {
eventSource.addEventListener('message', callback);
}
return () => {
if (eventSource) {
eventSource.removeEventListener('message', callback);
}
};
}, [eventSource]);
const getSnapshot = useCallback(() => data, [data]);
const serverSnapshot = useCallback(() => null, []);
const value = useSyncExternalStore(subscribe, getSnapshot, serverSnapshot);
return value;
}
function RealTimeDataComponent() {
const realTimeData = useSSE('/api/sse'); // O'zingizning SSE manzilingiz bilan almashtiring
if (!realTimeData) {
return <p>Loading...</p>;
}
return <div><p>Real-time Data: {JSON.stringify(realTimeData)}</p></div>;
}
export default RealTimeDataComponent;
Tushuntirish:
useSSE: Berilgan URL manziliga SSE ulanishini o'rnatadigan maxsus hook.subscribe: Serverdan yangi xabarlar haqida bildirishnoma olish uchunEventSourceobyektiga hodisa tinglovchisini qo'shadi. Har bir renderda qayta chaqiruv funksiyasi qayta yaratilmasligini ta'minlash uchunuseCallback'dan foydalanadi.getSnapshot: SSE oqimidan eng so'nggi olingan ma'lumotlarni qaytaradi.serverSnapshot: Server tomonida renderlash uchunnullqaytaradi.RealTimeDataComponent: Real vaqtdagi ma'lumotlarni ko'rsatish uchunuseSSEhook'idan foydalanadigan komponent.
3. IndexedDB bilan integratsiya
useSyncExternalStore yordamida React komponentlarini IndexedDB'da saqlangan ma'lumotlar bilan sinxronlashtiring.
import { useSyncExternalStore, useState, useEffect, useCallback } from 'react';
interface IDBData {
id: number;
name: string;
}
async function getAllData(): Promise {
return new Promise((resolve, reject) => {
const request = indexedDB.open('myDataBase', 1); // O'zingizning ma'lumotlar bazasi nomi va versiyasi bilan almashtiring
request.onerror = (event) => {
console.error("IndexedDB open error:", event);
reject(event);
};
request.onsuccess = (event) => {
const db = (event.target as IDBRequest).result as IDBDatabase;
const transaction = db.transaction(['myDataStore'], 'readonly'); // O'zingizning ombor nomingiz bilan almashtiring
const objectStore = transaction.objectStore('myDataStore');
const getAllRequest = objectStore.getAll();
getAllRequest.onsuccess = (event) => {
const data = (event.target as IDBRequest).result as IDBData[];
resolve(data);
};
getAllRequest.onerror = (event) => {
console.error("IndexedDB getAll error:", event);
reject(event);
};
};
request.onupgradeneeded = (event) => {
const db = (event.target as IDBRequest).result as IDBDatabase;
db.createObjectStore('myDataStore', { keyPath: 'id' });
};
});
}
function useIndexedDBData(): IDBData[] | null {
const [data, setData] = useState(null);
const [dbInitialized, setDbInitialized] = useState(false);
useEffect(() => {
const initDB = async () => {
try{
await getAllData();
setDbInitialized(true);
} catch (e) {
console.error("IndexedDB initialization failed", e);
}
}
initDB();
}, []);
const subscribe = useCallback((callback: () => void) => {
// Haddan tashqari qayta renderlashlarning oldini olish uchun qayta chaqiruvni kechiktiring.
let timeoutId: NodeJS.Timeout;
const debouncedCallback = () => {
clearTimeout(timeoutId);
timeoutId = setTimeout(callback, 50); // Kechiktirish vaqtini kerak bo'lganda sozlang
};
const handleVisibilityChange = () => {
// Yorliq yana ko'rinadigan bo'lganda ma'lumotlarni qayta yuklash
if (document.visibilityState === 'visible') {
debouncedCallback();
}
};
window.addEventListener('focus', debouncedCallback);
document.addEventListener('visibilitychange', handleVisibilityChange);
return () => {
window.removeEventListener('focus', debouncedCallback);
document.removeEventListener('visibilitychange', handleVisibilityChange);
clearTimeout(timeoutId);
};
}, []);
const getSnapshot = useCallback(() => {
// getSnapshot chaqirilgan har safar IndexedDB'dan eng so'nggi ma'lumotlarni olish
getAllData().then(newData => setData(newData));
return data;
}, [data]);
const serverSnapshot = useCallback(() => null, []);
return useSyncExternalStore(subscribe, getSnapshot, serverSnapshot);
}
function IndexedDBComponent() {
const data = useIndexedDBData();
if (!data) {
return <p>Loading data from IndexedDB...</p>;
}
return (
<div>
<h2>Data from IndexedDB:</h2>
<ul>
{data.map((item) => (
<li key={item.id}>{item.name} (ID: {item.id})</li>
))}
</ul>
</div>
);
}
export default IndexedDBComponent;
Tushuntirish:
getAllData: IndexedDB omboridan barcha ma'lumotlarni oladigan asinxron funksiya.useIndexedDBData: IndexedDB'dagi o'zgarishlarga obuna bo'lish uchunuseSyncExternalStore'dan foydalanadigan maxsus hook.subscribe: IndexedDB'dan ma'lumotlarni yangilash uchun ko'rinuvchanlik va fokus o'zgarishlari uchun tinglovchilarni o'rnatadi va haddan tashqari yangilanishlarning oldini olish uchun kechiktirish (debounce) funksiyasidan foydalanadi.getSnapshot: `getAllData()`'ni chaqirib joriy oniy tasvirni oladi va so'ngra holatdan `data`'ni qaytaradi.serverSnapshot: Server tomonida renderlash uchunnullqaytaradi.IndexedDBComponent: IndexedDB'dan ma'lumotlarni ko'rsatadigan komponent.
IndexedDB uchun muhim mulohazalar:
- Asinxron operatsiyalar: IndexedDB bilan o'zaro aloqalar asinxron, shuning uchun ma'lumotlarni olish va yangilashning asinxron tabiatini ehtiyotkorlik bilan boshqarishingiz kerak.
- Xatolarni qayta ishlash: Ma'lumotlar bazasiga kirishda yuzaga kelishi mumkin bo'lgan muammolarni, masalan, ma'lumotlar bazasi topilmaganligi yoki ruxsat xatolarini chiroyli tarzda hal qilish uchun mustahkam xatolarni qayta ishlashni amalga oshiring.
- Ma'lumotlar bazasi versiyalari: Ilovangiz rivojlanib borishi bilan ma'lumotlarning mosligini ta'minlash uchun
onupgradeneededhodisasidan foydalanib, ma'lumotlar bazasi versiyalarini ehtiyotkorlik bilan boshqaring. - Samaradorlik: IndexedDB operatsiyalari, ayniqsa katta hajmdagi ma'lumotlar to'plamlari uchun nisbatan sekin bo'lishi mumkin. Samaradorlikni oshirish uchun so'rovlar va indekslashni optimallashtiring.
Samaradorlikka oid mulohazalar
useSyncExternalStore samaradorlik uchun optimallashtirilgan bo'lsa-da, yodda tutish kerak bo'lgan ba'zi mulohazalar mavjud:
- Oniy tasvir (Snapshot) o'zgarishlarini minimallashtiring:
getSnapshotfunksiyasi faqat ma'lumotlar haqiqatda o'zgarganda yangi oniy tasvirni qaytarishiga ishonch hosil qiling. Keraksiz yangi obyektlar yoki massivlar yaratishdan saqlaning. Oniy tasvir yaratishni optimallashtirish uchun memoizatsiya usullaridan foydalanishni o'ylab ko'ring. - Yangilanishlarni to'plash (Batch Updates): Iloji bo'lsa, qayta renderlashlar sonini kamaytirish uchun tashqi omborga yangilanishlarni to'plang. Masalan, agar siz ombordagi bir nechta xususiyatni yangilayotgan bo'lsangiz, ularning barchasini bitta tranzaksiyada yangilashga harakat qiling.
- Kechiktirish/Cheklash (Debouncing/Throttling): Agar tashqi ombor tez-tez o'zgarsa, React komponentiga yangilanishlarni kechiktirish yoki cheklashni o'ylab ko'ring. Bu haddan tashqari qayta renderlashlarning oldini oladi va samaradorlikni oshiradi. Bu, ayniqsa, brauzer oynasining o'lchamini o'zgartirish kabi o'zgaruvchan omborlar bilan foydalidir.
- Yuzaki taqqoslash (Shallow Comparison): React ma'lumotlar o'zgarganligini yuzaki taqqoslash yordamida tezda aniqlashi uchun
getSnapshot'da primitiv qiymatlar yoki o'zgarmas obyektlarni qaytarayotganingizga ishonch hosil qiling. - Shartli yangilanishlar: Tashqi ombor tez-tez o'zgaradigan, lekin sizning komponentingiz faqat ma'lum o'zgarishlarga reaksiya bildirishi kerak bo'lgan hollarda, keraksiz qayta renderlashlarning oldini olish uchun `subscribe` funksiyasi ichida shartli yangilanishlarni amalga oshirishni ko'rib chiqing.
Umumiy xatolar va ularni bartaraf etish
- "Tearing" (uzilish) muammolari: Agar siz
useSyncExternalStore'dan foydalangandan keyin ham "tearing" muammolariga duch kelsangiz,getSnapshotfunksiyangiz ma'lumotlarning izchil ko'rinishini qaytarayotganini vasubscribefunksiyasi React'ni o'zgarishlar haqida to'g'ri xabardor qilayotganini qayta tekshiring.getSnapshotfunksiyasi ichida ma'lumotlarni to'g'ridan-to'g'ri o'zgartirmayotganingizga ishonch hosil qiling. - Cheksiz tsikllar: Agar
getSnapshotfunksiyasi ma'lumotlar o'zgarmagan bo'lsa ham har doim yangi qiymat qaytarsa, cheksiz tsikl yuzaga kelishi mumkin. Bu keraksiz yangi obyektlar yoki massivlar yaratganingizda sodir bo'lishi mumkin. Agar ma'lumotlar o'zgarmagan bo'lsa, bir xil qiymatni qaytarayotganingizga ishonch hosil qiling. - Server tomonida renderlashning yo'qligi: Agar siz server tomonida renderlashdan foydalanayotgan bo'lsangiz, komponentning serverda to'g'ri renderlanishini ta'minlash uchun
getServerSnapshotfunksiyasini taqdim etganingizga ishonch hosil qiling. Bu funksiya tashqi omborning dastlabki holatini qaytarishi kerak. - Noto'g'ri obunani bekor qilish: Har doim
subscribetomonidan qaytarilgan funksiya ichida tashqi ombordan obunani to'g'ri bekor qilganingizga ishonch hosil qiling. Buni qilmaslik xotira sizib chiqishiga olib kelishi mumkin. - Bir vaqtda rejim (Concurrent Mode) bilan noto'g'ri foydalanish: Tashqi omboringiz Bir vaqtda rejim bilan mos kelishiga ishonch hosil qiling. React renderlash paytida tashqi omborda o'zgartirishlar qilishdan saqlaning. O'zgartirishlar sinxron va bashorat qilinadigan bo'lishi kerak.
Xulosa
useSyncExternalStore — bu React komponentlarini tashqi ma'lumotlar omborlari bilan sinxronlashtirish uchun kuchli vosita. Uning qanday ishlashini tushunib, eng yaxshi amaliyotlarga rioya qilish orqali, siz hatto murakkab bir vaqtda renderlash stsenariylarida ham komponentlaringiz izchil va dolzarb ma'lumotlarni ko'rsatishini ta'minlashingiz mumkin. Bu hook uchinchi tomon holatni boshqarish kutubxonalaridan tortib, brauzer API'lari va real vaqtdagi ma'lumotlar oqimlarigacha bo'lgan turli xil ma'lumotlar manbalari bilan integratsiyani soddalashtiradi, bu esa yanada mustahkam va samarali React ilovalariga olib keladi. Har doim potentsial xatolarni bartaraf etishni, samaradorlikni optimallashtirishni va umumiy xatolardan qochish uchun obunalarni ehtiyotkorlik bilan boshqarishni unutmang.